package com.google.code.p.restrpc.http;

import java.io.IOException;
import java.net.URI;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.client.methods.HttpUriRequest;

/**
 * @author dz
 */
public class HttpClientTemplate implements HttpTemplate {
    
    private static final Log logger = LogFactory.getLog(HttpClientTemplate.class);
    
    private HttpClientFactory httpClientFactory;
    
    private ResponseErrorHandler errorHandler = new DefaultResponseErrorHandler();
    
    private Properties properties;
    
    public HttpClientTemplate() {
        this.httpClientFactory = HttpClientFactoryFactory.getCachedHttpClientFactory();
    }
    
    public HttpClientTemplate(HttpClientFactory httpClientFactory) {
        this.httpClientFactory = httpClientFactory;
    }
    
    public void setHttpClientFactory(HttpClientFactory httpClientFactory) {
        if (httpClientFactory == null) {
            throw new IllegalArgumentException("'httpClientFactory' must not be null");
        }
        this.httpClientFactory = httpClientFactory;
    }
    
    protected HttpClientFactory getHttpClientFactory() {
        return httpClientFactory;
    }
    
    protected Properties getProperties() {
        return properties;
    }
    
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
    
    /** Set the error handler. */
    public void setErrorHandler(ResponseErrorHandler errorHandler) {
        if (errorHandler == null) {
            throw new IllegalArgumentException("'errorHandler' must not be null");
        }
        this.errorHandler = errorHandler;
    }
    
    protected HttpClient getHttpClient() {
        return getHttpClientFactory().getHttpClient();
    }
    
    /**
     * Return the error handler. By default, this is the
     * {@link DefaultResponseErrorHandler}.
     */
    public ResponseErrorHandler getErrorHandler() {
        return this.errorHandler;
    }
    
    public HttpClientAgent createRequest(URI uri, HttpMethod httpMethod) throws IOException {
        HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
        return new DefaultHttpClientAgent(getHttpClient(), httpRequest);
    }
    
    /**
     * Create a Commons HttpMethodBase object for the given HTTP method and URI
     * specification.
     * 
     * @param httpMethod
     *            the HTTP method
     * @param uri
     *            the URI
     * @return the Commons HttpMethodBase object
     */
    protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
        switch (httpMethod) {
            case GET:
                return new HttpGet(uri);
            case DELETE:
                return new HttpDelete(uri);
            case HEAD:
                return new HttpHead(uri);
            case OPTIONS:
                return new HttpOptions(uri);
            case POST:
                return new HttpPost(uri);
            case PUT:
                return new HttpPut(uri);
            case TRACE:
                return new HttpTrace(uri);
            default:
                throw new IllegalArgumentException("Invalid HTTP method: " + httpMethod);
        }
    }
    
    public Object execute(final URI url,
                          final HttpMethod method,
                          final HttpRequestCallback requestCallback,
                          final HttpResponseCallback responseCallback,
                          final ResponseErrorHandler errorHandler) {
        if (url == null) {
            throw new IllegalArgumentException("'url' must not be null");
        }
        if (method == null) {
            throw new IllegalArgumentException("'method' must not be null");
        }
        HttpClientResponse response = null;
        try {
            HttpClientAgent request = createRequest(url, method);
            if (requestCallback != null) {
                requestCallback.doWith(request.getHttpClientRequest());
            }
            response = request.execute();
            if (errorHandler != null) {
                if (!errorHandler.hasError(response)) {
                    logResponseStatus(method, url, response);
                }
                else {
                    logResponseError(method, url, response);
                    errorHandler.handleError(response);
                }
            }
            else {
                if (!getErrorHandler().hasError(response)) {
                    logResponseStatus(method, url, response);
                }
                else {
                    logResponseError(method, url, response);
                    getErrorHandler().handleError(response);
                }
            }
            
            // if not exception throws from error handler, responseCallback will
            // convert the body to object
            if (responseCallback != null) {
                return responseCallback.doWith(response);
            }
            
            return null;
        }
        catch (IOException ex) {
            throw new HttpConnectException("I/O error: " + ex.getMessage(), ex);
        }
        finally {
            if (response != null) {
                try {
                    response.close();
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    private void logResponseStatus(HttpMethod method,
                                   URI url,
                                   HttpClientResponse response) {
        if (logger.isDebugEnabled()) {
            try {
                logger.debug(method.name() + " request for \""
                             + url
                             + "\" resulted in "
                             + response.getStatusCode()
                             + " ("
                             + response.getStatusText()
                             + ")");
            }
            catch (IOException e) {
                // ignore
            }
        }
    }
    
    private void logResponseError(HttpMethod method,
                                  URI url,
                                  HttpClientResponse response) throws IOException {
        if (logger.isWarnEnabled()) {
            try {
                logger.warn(method.name() + " request for \""
                            + url
                            + "\" resulted in "
                            + response.getStatusCode()
                            + " ("
                            + response.getStatusText()
                            + "); invoking error handler");
            }
            catch (IOException e) {
                // ignore
            }
        }
    }
    
}
